﻿using System.Diagnostics.CodeAnalysis;
using HomeDump.Core.Contracts;

namespace System.Threading.Tasks
{
	/// <summary>
	/// Class containing <see cref="Task"/> extensions.
	/// </summary>
	/// <remarks>
	/// Code originated from http://blogs.msdn.com/b/pfxteam/archive/2010/11/21/10094564.aspx.
	/// </remarks>
	public static class TaskExtensions
	{
		/// <summary>
		/// When the first task completes, the next action is invoked to produce a task and is provided
		/// with the output from the previous task.  The task returned from Then primarily represents
		/// the task returned by the next function’s invocation; however, if the first task is canceled
		/// or faults, the next function will not be invoked, and the returned task will instead
		/// represent the first task.
		/// </summary>
		/// <param name="first">The first task.</param>
		/// <param name="next">The next action.</param>
		/// <param name="scheduler">
		/// The scheduler to use, if no one is specified the default task scheduler is used.
		/// </param>
		/// <returns>A task without a result.</returns>
		[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes",
			Justification = "Exception is reported in the task.")]
		public static Task Then(
			this Task first,
			Action next,
			TaskScheduler scheduler = null)
		{
			Requires.IsNotNull(first);
			Requires.IsNotNull(next);

			scheduler = scheduler ?? TaskScheduler.Default;
			var tcs = new TaskCompletionSource<object>();

			first.ContinueWith(
				delegate
				{
					if (first.IsFaulted)
					{
						tcs.TrySetException(first.Exception.InnerExceptions);
					}
					else if (first.IsCanceled)
					{
						tcs.TrySetCanceled();
					}
					else
					{
						try
						{
							next();
							tcs.TrySetResult(null);
						}
						catch (TaskCanceledException)
						{
							tcs.TrySetCanceled();
						}
						catch (Exception e)
						{
							tcs.TrySetException(e);
						}
					}
				},
				CancellationToken.None,
				TaskContinuationOptions.ExecuteSynchronously,
				scheduler);

			return tcs.Task;
		}


		/// <summary>
		/// When the first task completes, the next action is invoked to produce a task and is provided
		/// with the output from the previous task.  The task returned from Then primarily represents
		/// the task returned by the next function’s invocation; however, if the first task is canceled
		/// or faults, the next function will not be invoked, and the returned task will instead
		/// represent the first task.
		/// </summary>
		/// <typeparam name="T">The task result type.</typeparam>
		/// <param name="first">The first task.</param>
		/// <param name="next">The next action.</param>
		/// <param name="scheduler">
		/// The scheduler to use, if no one is specified the default task scheduler is used.
		/// </param>
		/// <returns>A task without a result.</returns>
		[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes",
			Justification = "Exception is reported in the task.")]
		public static Task Then<T>(
			this Task<T> first,
			Action<T> next,
			TaskScheduler scheduler = null)
		{
			Requires.IsNotNull(first);
			Requires.IsNotNull(next);

			scheduler = scheduler ?? TaskScheduler.Default;
			var tcs = new TaskCompletionSource<object>();

			first.ContinueWith(
				delegate
				{
					if (first.IsFaulted)
					{
						tcs.TrySetException(first.Exception.InnerExceptions);
					}
					else if (first.IsCanceled)
					{
						tcs.TrySetCanceled();
					}
					else
					{
						try
						{
							next(first.Result);
							tcs.TrySetResult(null);
						}
						catch (TaskCanceledException)
						{
							tcs.TrySetCanceled();
						}
						catch (Exception e)
						{
							tcs.TrySetException(e);
						}
					}
				},
				CancellationToken.None,
				TaskContinuationOptions.ExecuteSynchronously,
				scheduler);

			return tcs.Task;
		}


		/// <summary>
		/// When the first task completes, the next function is invoked to produce a task and is provided
		/// with the output from the previous task.  The task returned from Then primarily represents
		/// the task returned by the next function’s invocation; however, if the first task is canceled
		/// or faults, the next function will not be invoked, and the returned task will instead
		/// represent the first task.
		/// </summary>
		/// <param name="first">The first task.</param>
		/// <param name="next">The next task function.</param>
		/// <param name="scheduler">
		/// The scheduler to use, if no one is specified the default task scheduler is used.
		/// </param>
		/// <returns>A task without a result.</returns>
		[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes",
			Justification = "Exception is reported in the task.")]
		public static Task Then(
			this Task first,
			Func<Task> next,
			TaskScheduler scheduler = null)
		{
			Requires.IsNotNull(first);
			Requires.IsNotNull(next);

			scheduler = scheduler ?? TaskScheduler.Default;
			var tcs = new TaskCompletionSource<object>();

			first.ContinueWith(
				delegate
				{
					if (first.IsFaulted)
					{
						tcs.TrySetException(first.Exception.InnerExceptions);
					}
					else if (first.IsCanceled)
					{
						tcs.TrySetCanceled();
					}
					else
					{
						try
						{
							var t = next();
							t.ContinueWith(
								delegate
								{
									if (t.IsFaulted)
									{
										tcs.TrySetException(t.Exception.InnerExceptions);
									}
									else if (t.IsCanceled)
									{
										tcs.TrySetCanceled();
									}
									else
									{
										tcs.TrySetResult(null);
									}
								},
								TaskContinuationOptions.ExecuteSynchronously);
						}
						catch (TaskCanceledException)
						{
							tcs.TrySetCanceled();
						}
						catch (Exception e)
						{
							tcs.TrySetException(e);
						}
					}
				},
				CancellationToken.None,
				TaskContinuationOptions.ExecuteSynchronously,
				scheduler);

			return tcs.Task;
		}


		/// <summary>
		/// When the first task completes, the next function is invoked to produce a task and is provided
		/// with the output from the previous task.  The task returned from Then primarily represents
		/// the task returned by the next function’s invocation; however, if the first task is canceled
		/// or faults, the next function will not be invoked, and the returned task will instead
		/// represent the first task.
		/// </summary>
		/// <typeparam name="T">The task result type.</typeparam>
		/// <param name="first">The first task.</param>
		/// <param name="next">The next task function.</param>
		/// <param name="scheduler">
		/// The scheduler to use, if no one is specified the default task scheduler is used.
		/// </param>
		/// <returns>A task without a result.</returns>
		[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes",
			Justification = "Exception is reported in the task.")]
		public static Task Then<T>(
			this Task<T> first,
			Func<T, Task> next,
			TaskScheduler scheduler = null)
		{
			Requires.IsNotNull(first);
			Requires.IsNotNull(next);

			scheduler = scheduler ?? TaskScheduler.Default;
			var tcs = new TaskCompletionSource<object>();

			first.ContinueWith(
				delegate
				{
					if (first.IsFaulted)
					{
						tcs.TrySetException(first.Exception.InnerExceptions);
					}
					else if (first.IsCanceled)
					{
						tcs.TrySetCanceled();
					}
					else
					{
						try
						{
							var t = next(first.Result);
							t.ContinueWith(
								delegate
								{
									if (t.IsFaulted)
									{
										tcs.TrySetException(t.Exception.InnerExceptions);
									}
									else if (t.IsCanceled)
									{
										tcs.TrySetCanceled();
									}
									else
									{
										tcs.TrySetResult(null);
									}
								},
								TaskContinuationOptions.ExecuteSynchronously);
						}
						catch (TaskCanceledException)
						{
							tcs.TrySetCanceled();
						}
						catch (Exception e)
						{
							tcs.TrySetException(e);
						}
					}
				},
				CancellationToken.None,
				TaskContinuationOptions.ExecuteSynchronously,
				scheduler);

			return tcs.Task;
		}


		/// <summary>
		/// When the first task completes, the next function is invoked to produce a task and is provided
		/// with the output from the previous task.  The task returned from Then primarily represents
		/// the task returned by the next function’s invocation; however, if the first task is canceled
		/// or faults, the next function will not be invoked, and the returned task will instead
		/// represent the first task.
		/// </summary>
		/// <typeparam name="T">The task result type.</typeparam>
		/// <param name="first">The first task.</param>
		/// <param name="next">The next function.</param>
		/// <param name="scheduler">
		/// The scheduler to use, if no one is specified the default task scheduler is used.
		/// </param>
		/// <returns>A task with a result of T.</returns>
		[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes",
			Justification = "Exception is reported in the task.")]
		public static Task<T> Then<T>(
			this Task first,
			Func<T> next,
			TaskScheduler scheduler = null)
		{
			Requires.IsNotNull(first);
			Requires.IsNotNull(next);

			scheduler = scheduler ?? TaskScheduler.Default;
			var tcs = new TaskCompletionSource<T>();

			first.ContinueWith(
				delegate
				{
					if (first.IsFaulted)
					{
						tcs.TrySetException(first.Exception.InnerExceptions);
					}
					else if (first.IsCanceled)
					{
						tcs.TrySetCanceled();
					}
					else
					{
						try
						{
							var t = next();
							tcs.TrySetResult(t);
						}
						catch (TaskCanceledException)
						{
							tcs.TrySetCanceled();
						}
						catch (Exception e)
						{
							tcs.TrySetException(e);
						}
					}
				},
				CancellationToken.None,
				TaskContinuationOptions.ExecuteSynchronously,
				scheduler);

			return tcs.Task;
		}


		/// <summary>
		/// When the first task completes, the next function is invoked to produce a task and is provided
		/// with the output from the previous task.  The task returned from Then primarily represents
		/// the task returned by the next function’s invocation; however, if the first task is canceled
		/// or faults, the next function will not be invoked, and the returned task will instead
		/// represent the first task.
		/// </summary>
		/// <typeparam name="T">The task result type.</typeparam>
		/// <param name="first">The first task.</param>
		/// <param name="next">The next task function.</param>
		/// <param name="scheduler">
		/// The scheduler to use, if no one is specified the default task scheduler is used.
		/// </param>
		/// <returns>A task with a result of T.</returns>
		[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes",
			Justification = "Exception is reported in the task.")]
		public static Task<T> Then<T>(
			this Task first,
			Func<Task<T>> next,
			TaskScheduler scheduler = null)
		{
			Requires.IsNotNull(first);
			Requires.IsNotNull(next);

			scheduler = scheduler ?? TaskScheduler.Default;
			var tcs = new TaskCompletionSource<T>();

			first.ContinueWith(
				delegate
				{
					if (first.IsFaulted)
					{
						tcs.TrySetException(first.Exception.InnerExceptions);
					}
					else if (first.IsCanceled)
					{
						tcs.TrySetCanceled();
					}
					else
					{
						try
						{
							var t = next();
							t.ContinueWith(
								delegate
								{
									if (t.IsFaulted)
									{
										tcs.TrySetException(t.Exception.InnerExceptions);
									}
									else if (t.IsCanceled)
									{
										tcs.TrySetCanceled();
									}
									else
									{
										tcs.TrySetResult(t.Result);
									}
								},
								TaskContinuationOptions.ExecuteSynchronously);
						}
						catch (TaskCanceledException)
						{
							tcs.TrySetCanceled();
						}
						catch (Exception e)
						{
							tcs.TrySetException(e);
						}
					}
				},
				CancellationToken.None,
				TaskContinuationOptions.ExecuteSynchronously,
				scheduler);

			return tcs.Task;
		}


		/// <summary>
		/// When the first task completes, the next function is invoked to produce a task and is provided
		/// with the output from the previous task.  The task returned from Then primarily represents
		/// the task returned by the next function’s invocation; however, if the first task is canceled
		/// or faults, the next function will not be invoked, and the returned task will instead
		/// represent the first task.
		/// </summary>
		/// <typeparam name="T1">The result type of the first task.</typeparam>
		/// <typeparam name="T2">The result type of the next task.</typeparam>
		/// <param name="first">The first task.</param>
		/// <param name="next">The next task function.</param>
		/// <param name="scheduler">
		/// The scheduler to use, if no one is specified the default task scheduler is used.
		/// </param>
		/// <returns>A task with a result of T2.</returns>
		[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes",
			Justification = "Exception is reported in the task.")]
		public static Task<T2> Then<T1, T2>(
			this Task<T1> first,
			Func<T1, Task<T2>> next,
			TaskScheduler scheduler = null)
		{
			Requires.IsNotNull(first);
			Requires.IsNotNull(next);

			scheduler = scheduler ?? TaskScheduler.Default;
			var tcs = new TaskCompletionSource<T2>();

			first.ContinueWith(
				delegate
				{
					if (first.IsFaulted)
					{
						tcs.TrySetException(first.Exception.InnerExceptions);
					}
					else if (first.IsCanceled)
					{
						tcs.TrySetCanceled();
					}
					else
					{
						try
						{
							var t = next(first.Result);
							t.ContinueWith(
								delegate
								{
									if (t.IsFaulted)
									{
										tcs.TrySetException(t.Exception.InnerExceptions);
									}
									else if (t.IsCanceled)
									{
										tcs.TrySetCanceled();
									}
									else
									{
										tcs.TrySetResult(t.Result);
									}
								},
								TaskContinuationOptions.ExecuteSynchronously);
						}
						catch (TaskCanceledException)
						{
							tcs.TrySetCanceled();
						}
						catch (Exception e)
						{
							tcs.TrySetException(e);
						}
					}
				},
				CancellationToken.None,
				TaskContinuationOptions.ExecuteSynchronously,
				scheduler);

			return tcs.Task;
		}


		/// <summary>
		/// When the first task completes, the next function is invoked to produce a task and is provided
		/// with the output from the previous task.  The task returned from Then primarily represents
		/// the task returned by the next function’s invocation; however, if the first task is canceled
		/// or faults, the next function will not be invoked, and the returned task will instead
		/// represent the first task.
		/// </summary>
		/// <typeparam name="T1">The result type of the first task.</typeparam>
		/// <typeparam name="T2">The result type of the next function.</typeparam>
		/// <param name="first">The first task.</param>
		/// <param name="next">The next task function.</param>
		/// <param name="scheduler">
		/// The scheduler to use, if no one is specified the default task scheduler is used.
		/// </param>
		/// <returns>A task with a result of T2.</returns>
		[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes",
			Justification = "Exception is reported in the task.")]
		public static Task<T2> Then<T1, T2>(
			this Task<T1> first,
			Func<T1, T2> next,
			TaskScheduler scheduler = null)
		{
			Requires.IsNotNull(first);
			Requires.IsNotNull(next);

			scheduler = scheduler ?? TaskScheduler.Default;
			var tcs = new TaskCompletionSource<T2>();

			first.ContinueWith(
				delegate
				{
					if (first.IsFaulted)
					{
						tcs.TrySetException(first.Exception.InnerExceptions);
					}
					else if (first.IsCanceled)
					{
						tcs.TrySetCanceled();
					}
					else
					{
						try
						{
							var t = next(first.Result);
							tcs.TrySetResult(t);
						}
						catch (TaskCanceledException)
						{
							tcs.TrySetCanceled();
						}
						catch (Exception e)
						{
							tcs.TrySetException(e);
						}
					}
				},
				CancellationToken.None,
				TaskContinuationOptions.ExecuteSynchronously,
				scheduler);

			return tcs.Task;
		}
	}
}